package com.flow.framework.schedule.system.listener.lifecycle;

import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.system.initialization.ApplicationContextHelper;
import com.flow.framework.core.system.listener.lifecycle.ISystemLifecycleListener;
import com.flow.framework.schedule.converter.ConfigPropertiesConverter;
import com.flow.framework.schedule.executor.LocalScheduleJobExecutor;
import com.flow.framework.schedule.holder.LocalSchedulerHolder;
import com.flow.framework.schedule.job.BaseScheduleJob;
import com.flow.framework.schedule.properties.FrameworkScheduleConfigProperties;
import com.flow.framework.schedule.properties.local.LocalConfigProperties;
import com.flow.framework.schedule.properties.local.sub.SubLocalConfigProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 调度生命周期监听器
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/4/10
 */
@Slf4j
@RequiredArgsConstructor
public class LocalScheduleLifecycleListener implements ISystemLifecycleListener {

    private final FrameworkScheduleConfigProperties frameworkScheduleConfigProperties;

    @Override
    public void onStartUp() {
        JobResourceHolder.cacheBeans(ApplicationContextHelper.getBeanNameAndBeanMap(BaseScheduleJob.class));
        onRefresh();
    }

    @Override
    public synchronized void onRefresh() {
        List<LocalConfigProperties> locals = ConfigPropertiesConverter.toNewLocalConfigPropertiesList(frameworkScheduleConfigProperties.getLocals());
        Map<String, LocalConfigProperties> lastJobNameAndLocalConfigMap = JobResourceHolder.getJobNameAndConfigMap();
        if (VerifyUtil.isEmpty(locals) && VerifyUtil.isEmpty(lastJobNameAndLocalConfigMap)) {
            return;
        }
        preCheck(locals);
        Map<String, LocalConfigProperties> latestJobNameAndConfigMap = Collections.emptyMap();
        if (!VerifyUtil.isEmpty(locals)) {
            latestJobNameAndConfigMap = locals.stream()
                    .collect(Collectors.toMap(this::getJobName, (value) -> value));
        }
        Map<String, LocalConfigProperties> addJobNameAndConfigMap = new HashMap<>(latestJobNameAndConfigMap);
        Map<String, LocalConfigProperties> deleteJobNameAndConfigMap = new HashMap<>(lastJobNameAndLocalConfigMap);

        // 当前addJobNameAndConfigMap中是最新的job配置，如果把里面包含的老的配置剔除掉，则剩下的全为需要新增job的配置
        lastJobNameAndLocalConfigMap.forEach((jobName, localConfigProperties) -> addJobNameAndConfigMap.remove(jobName));

        // 当前deleteJobNameAndConfigMap中是老的job配置，如果把里面包含的新的配置剔除掉，则剩下的全为需要删除job的配置
        latestJobNameAndConfigMap.forEach((jobName, localConfigProperties) -> deleteJobNameAndConfigMap.remove(jobName));

        // 如果需要新增的和需要删除的都为空，则说明配置没有变化，不需要做任何处理，但有可能子任务配置已经更新，故需要重新设置最新的配置
        if (addJobNameAndConfigMap.isEmpty() && deleteJobNameAndConfigMap.isEmpty()) {
            JobResourceHolder.setJobNameAndConfigMap(latestJobNameAndConfigMap);
            return;
        }
        deleteJobNameAndConfigMap.forEach((jobName, localConfigProperties) -> {
            try {
                LocalSchedulerHolder.getInstance().deleteJob(JobKey.jobKey(jobName, localConfigProperties.getBeanName()));
            } catch (SchedulerException e) {
                log.warn("remove job error.", e);
            }
        });
        JobResourceHolder.setJobNameAndConfigMap(latestJobNameAndConfigMap);
        schedule(addJobNameAndConfigMap);
    }

    private synchronized void schedule(Map<String, LocalConfigProperties> addJobNameAndConfigMap) {
        if (VerifyUtil.isEmpty(addJobNameAndConfigMap)) {
            return;
        }

        addJobNameAndConfigMap.forEach((jobName, localConfigProperties) -> {
            String beanName = localConfigProperties.getBeanName();
            JobDetail jobDetail = JobBuilder.newJob(LocalScheduleJobExecutor.class).withIdentity(jobName, beanName).build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobName, beanName)
                    .withSchedule(CronScheduleBuilder.cronSchedule(localConfigProperties.getCron()))
                    .startNow()
                    .build();

            try {
                LocalSchedulerHolder.getInstance().scheduleJob(jobDetail, trigger);
            } catch (SchedulerException e) {
                log.error("schedule job error.", e);
                throw new CheckedException(SystemErrorCode.SCHEDULE_JOB_ERROR, "schedule job error.", e);
            }
        });
    }

    private void preCheck(List<LocalConfigProperties> locals) {
        if (VerifyUtil.isEmpty(locals)) {
            return;
        }
        locals.forEach(localConfigProperties -> {
            String beanName = localConfigProperties.getBeanName();
            if (VerifyUtil.hasEmpty(beanName, localConfigProperties.getAuthor(), localConfigProperties.getCron(),
                    localConfigProperties.getRemark())) {
                log.error("local config properties error, bean name : {}", beanName);
                throw new CheckedException(SystemErrorCode.SCHEDULE_JOB_ERROR);
            }
            if (!JobResourceHolder.isContainsBean(beanName)) {
                log.error("can't find job bean. bean name : {}", beanName);
                throw new CheckedException(SystemErrorCode.OBJECT_NOT_FOUND_ERROR);
            }
            List<SubLocalConfigProperties> subs = localConfigProperties.getSubs();
            if (VerifyUtil.isEmpty(subs)) {
                return;
            }
            subs.forEach(subLocalConfigProperties -> {
                String subBeanName = subLocalConfigProperties.getBeanName();
                if (!JobResourceHolder.isContainsBean(subBeanName)) {
                    log.error("can't find sub job bean. sub bean name : {}", subBeanName);
                    throw new CheckedException(SystemErrorCode.OBJECT_NOT_FOUND_ERROR);
                }
            });
        });
    }

    private String getJobName(LocalConfigProperties localConfigProperties) {
        return localConfigProperties.getBeanName().trim()
                + ":" + localConfigProperties.getCron().trim()
                + ":" + localConfigProperties.getParams().trim();
    }
}